package com.jpsl.core.reflection;

import com.jpsl.core.reflection.property.MetaClasses;
import com.jpsl.core.reflection.wrapper.BeanWrapper;
import com.jpsl.core.reflection.wrapper.MapWrapper;
import com.jpsl.core.reflection.wrapper.ObjectWrapper;
import com.jpsl.core.util.Assert;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.*;

/**
 * 属性映射
 *
 * @author 懒王猫
 */
public class PropertiesMapper {

    // 源对象
    private final Object source;

    // 目标对象
    private final Object target;

    // 配置信息
    private final Config config = new Config();

    private int metaMode = MetaClasses.BY_FIELD;

    private boolean deepMapping = false;

    public PropertiesMapper(Object source, Object target) {
        Assert.notNull(source, "source");
        Assert.notNull(target, "target");
        this.source = source;
        this.target = target;
    }

    public PropertiesMapper setMetaMode(int mode) {
        this.metaMode = mode;
        return this;
    }

    public PropertiesMapper setDeepMapping(boolean deepMapping) {
        this.deepMapping = deepMapping;
        return this;
    }

    public Config getConfig() {
        return config;
    }

    public void map() {
        ObjectWrapper sourceWrapper = getWrapper(source);
        ObjectWrapper targetWrapper = getWrapper(target);

        List<String> properties = sourceWrapper.getProperties();
        for (String property : properties) {
            if (sourceWrapper.hasGetter(property)) {
                // 获取源属性过滤器
                Predicate<Object> srcPropertyFilter = config.getSrcPropertyFilter(property);
                // 过滤处理
                Object srcValue = sourceWrapper.getValue(property);

                if (srcPropertyFilter.test(srcValue)) {
                    // 属性名称映射
                    Function<String, String> srcPropertyNameMapper = config.getSrcPropertyNameMapper(property);
                    String mappedProperty = srcPropertyNameMapper.apply(property);
                    // 目标属性处理
                    boolean maps = true;
                    Predicate<Object> tgtPropertyFilter = config.getTgtPropertyFilter(property);
                    if (tgtPropertyFilter != null && targetWrapper.hasGetter(mappedProperty)) {
                        Object tgtValue = targetWrapper.getValue(mappedProperty);
                        maps = tgtPropertyFilter.test(tgtValue);
                    }
                    if (maps) {
                        // 源属性值转换
                        Function<Object, Object> srcPropertyValueMapper = config.getSrcPropertyValueMapper(property);
                        Object adaptedValue = srcPropertyValueMapper.apply(srcValue);
                        if (targetWrapper.hasSetter(mappedProperty)) {
                            if (deepMapping) {
                                deepMapProps(mappedProperty, adaptedValue, targetWrapper);
                            } else {
                                targetWrapper.setValue(mappedProperty, adaptedValue);
                            }
                        }
                    }
                }
            }
        }

    }

    /**
     * 深度复制属性, 将类型为 bean 对象的属性深度再复制一次
     */
    private void deepMapProps(String property, Object value, ObjectWrapper targetWrapper) {
        Class<?> valueType;
        if (value != null && !TypeUtil.isPrimitive((valueType = value.getClass())) && !(value instanceof Collection)) {
            // 目标能 get 值, 且值存在
            if (targetWrapper.hasGetter(property)) {
                Object tgtValue = targetWrapper.getValue(property);
                Class<?> tgtGetterType;
                if (tgtValue == null && (tgtGetterType = targetWrapper.getGetterType(property)) != null) {
                    // 如果目标的属性类型是源属性类型的父类, 就直接用源属性类型创建对象
                    tgtValue = TypeUtil.newObject(tgtGetterType.isAssignableFrom(valueType) ? valueType : tgtGetterType);
                }
                // 属性对象也映射一次
                if (tgtValue != null) {
                    new PropertiesMapper(value, tgtValue).setMetaMode(metaMode).setDeepMapping(deepMapping).map();
                }
                targetWrapper.setValue(property, tgtValue);
            }
        } else {
            targetWrapper.setValue(property, value);
        }
    }

    @SuppressWarnings("unchecked")
    private ObjectWrapper getWrapper(Object object) {
        Assert.isTrue(!(object instanceof Collection), "object 不能是 collection");
        ObjectWrapper wrapper;
        if (object instanceof Map) {
            wrapper = new MapWrapper((Map<String, Object>) object);
        } else {
            wrapper = new BeanWrapper(object, MetaClasses.getMetaClass(object.getClass(), metaMode));
        }
        return wrapper;
    }

    /**
     * 对象属性映射配置类
     *
     * @author 懒王猫
     */
    public static class Config {

        // key: property name
        // 源对象属性过滤器集合
        private final Map<String, Predicate<Object>> srcPropertyFilters = new HashMap<>();
        // 源对象属性名称映射器集合
        private final Map<String, Function<String, String>> srcPropertyNameMappers = new HashMap<>();
        // 源对象属性值转换器集合
        private final Map<String, Function<Object, Object>> srcPropertyValueMappers = new HashMap<>();
        // 目标对象属性过滤器集合
        private final Map<String, Predicate<Object>> tgtPropertyFilters = new HashMap<>();

        // default
        // 源对象属性默认过滤器
        private Predicate<Object> defaultSrcPropertyFilter = o -> true;
        // 源对象属性名称默认映射器 (属性名称不变)
        private Function<String, String> defaultSrcPropertyNameMapper = s -> s;
        // 源对象属性值默认转换器 (属性值不变)
        private Function<Object, Object> defaultSrcPropertyValueMapper = o -> o;
        // 目标对象属性名称默认过滤器, 默认 null, 为了选择性不获取目标对象属性值
        private Predicate<Object> defaultTgtPropertyFilter;

        private Config() {
        }

        public Config setDefaultSrcPropertyFilter(Predicate<Object> defaultSrcPropertyFilter) {
            this.defaultSrcPropertyFilter = defaultSrcPropertyFilter;
            return this;
        }

        public Config setDefaultSrcPropertyNameMapper(Function<String, String> defaultSrcPropertyNameMapper) {
            this.defaultSrcPropertyNameMapper = defaultSrcPropertyNameMapper;
            return this;
        }

        public Config setDefaultSrcPropertyValueMapper(Function<Object, Object> defaultSrcPropertyValueMapper) {
            this.defaultSrcPropertyValueMapper = defaultSrcPropertyValueMapper;
            return this;
        }

        public Config setDefaultTgtPropertyFilter(Predicate<Object> defaultTgtPropertyFilter) {
            this.defaultTgtPropertyFilter = defaultTgtPropertyFilter;
            return this;
        }

        public Config addSrcPropertyFilter(String srcProperty, Predicate<Object> srcPropertyFilter) {
            this.srcPropertyFilters.put(srcProperty, srcPropertyFilter);
            return this;
        }

        public Config addSrcPropertyNameMapper(String srcProperty, Function<String, String> srcPropertyNameMapper) {
            this.srcPropertyNameMappers.put(srcProperty, srcPropertyNameMapper);
            return this;
        }

        public Config addSrcPropertyValueMapper(String srcProperty, Function<Object, Object> srcPropertyValueMapper) {
            this.srcPropertyValueMappers.put(srcProperty, srcPropertyValueMapper);
            return this;
        }

        public Config addTgtPropertyFilter(String tgtProperty, Predicate<Object> tgtPropertyFilter) {
            this.tgtPropertyFilters.put(tgtProperty, tgtPropertyFilter);
            return this;
        }

        public Predicate<Object> getSrcPropertyFilter(String srcProperty) {
            Predicate<Object> filter = srcPropertyFilters.get(srcProperty);
            return filter == null ? defaultSrcPropertyFilter : filter;
        }

        public Function<String, String> getSrcPropertyNameMapper(String srcProperty) {
            Function<String, String> mapper = srcPropertyNameMappers.get(srcProperty);
            return mapper == null ? defaultSrcPropertyNameMapper : mapper;
        }

        public Function<Object, Object> getSrcPropertyValueMapper(String srcProperty) {
            Function<Object, Object> mapper = srcPropertyValueMappers.get(srcProperty);
            return mapper == null ? defaultSrcPropertyValueMapper : mapper;
        }

        public Predicate<Object> getTgtPropertyFilter(String tgtProperty) {
            Predicate<Object> filter = tgtPropertyFilters.get(tgtProperty);
            return filter == null ? defaultTgtPropertyFilter : filter;
        }
    }

    static class TypeUtil {

        public static boolean isPrimitive(Class<?> type) {
            if (type.isPrimitive()) {
                return true;
            } else if (String.class == type) {
                return true;
            } else if (Byte.class == type) {
                return true;
            } else if (Short.class == type) {
                return true;
            } else if (Integer.class == type) {
                return true;
            } else if (Long.class == type) {
                return true;
            } else if (Float.class == type) {
                return true;
            } else if (Double.class == type) {
                return true;
            } else if (Boolean.class == type) {
                return true;
            } else if (BigDecimal.class == type) {
                return true;
            } else if (BigInteger.class == type) {
                return true;
            } else if (java.util.Date.class == type) {
                return true;
            } else if (java.sql.Date.class == type) {
                return true;
            } else if (java.sql.Time.class == type) {
                return true;
            } else if (java.sql.Timestamp.class == type) {
                return true;
            } else if (LocalDateTime.class == type) {
                return true;
            } else if (LocalDate.class == type) {
                return true;
            } else if (LocalTime.class == type) {
                return true;
            } else {
                return false;
            }
        }

        static Object newObject(Class<?> clazz) {
            try {
                if (Map.class.isAssignableFrom(clazz)) {
                    return new HashMap<>();
                }
                return clazz.newInstance();
            } catch (ReflectiveOperationException e) {
                return null;
            }
        }

    }

}
